package com.zhuiyun.educations.api.voide.service.impl;

import com.zhuiyun.educations.api.voide.entity.Voide;
import com.zhuiyun.educations.api.voide.entity.VoideResult;
import com.zhuiyun.educations.api.voide.mapper.VoideMapper;
import com.zhuiyun.educations.api.voide.service.VoideService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @ClassName VoideServiceImpl
 * @Description TODO
 * @Author yuanb
 * @Date 2023/6/23 11:48
 **/
@Service
@SuppressWarnings("all")
public class VoideServiceImpl implements VoideService {
    @Autowired
    private VoideMapper voideMapper;
    private Logger logger =  LoggerFactory.getLogger(VoideServiceImpl.class);
    @Value("${file.rootPath}")
    private String rootPath;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 检查文件是否存在，如果存在则跳过该文件的上传，如果不存在，返回需要上传的分片集合
     * 检查分片是否存在
     * ○ 检查目录下的文件是否存在。
     * ○ 检查redis存储的分片是否存在。
     * ○ 判断分片数量和总分片数量是否一致。
     * 如果文件存在并且分片上传完毕，标识已经完成附件的上传，可以进行秒传操作。
     * 如果文件不存在或者分片为上传完毕，则返回false并返回已经上传的分片信息。
     *
     * @param chunkDTO
     * @return
     */
//    
    @Override
    public VoideResult checkChunkExist(Voide chunkDTO) {
        // 1. 检查文件是否已上传过
        String fileFolderPath = getFileFolderPath(chunkDTO.getIdentifier());
        logger.info("文件夹路径: {}", fileFolderPath);
        String filePath = getFilePath(chunkDTO.getIdentifier(), chunkDTO.getFilename());
        File file = new File(fileFolderPath + filePath);
        boolean exists = file.exists();

        if (chunkDTO.getIdentifier() != null && exists) {
            // 分片存在，跳过已上传的分片
            Set<Integer> missingChunks = new HashSet<>();
            for (int i = 1; i <= chunkDTO.getTotalChunks(); i++) {
                File chunkFile = new File(fileFolderPath + getChunkFilePath(chunkDTO.getIdentifier(), i));
                if (!chunkFile.exists()) {
                    missingChunks.add(i);
                }
            }
            // 2. 检查Redis中是否存在，并且所有分片已经上传完成
//        Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "identifier");
//
//        if (chunkDTO.getIdentifier() != null && exists) {
//            // 分片存在，跳过已上传的分片
//            Set<Integer> missingChunks = new HashSet<>();
//            for (int i = 1; i <= chunkDTO.getTotalChunks(); i++) {
//                if (!uploaded.contains(i)) {
//                    missingChunks.add(i);
//                }
//            }
            // 检查是否所有分片都已上传完成
            if (missingChunks.isEmpty()) {
                return new VoideResult(true); // 所有分片已上传完成
            } else {
                return new VoideResult(false, missingChunks); // 返回缺失的分片
            }
        } else {
            // 2. 文件不存在，直接执行分片上传
            return new VoideResult(false, null); // 没有已上传的分片
        }
    }

    /**
     * 获取分片文件的路径
     *
     * @param identifier 分片标识符
     * @param chunkIndex 分片索引
     * @return 分片文件路径
     */
    private String getChunkFilePath(String identifier, int chunkIndex) {
        return identifier + File.separator + chunkIndex;
    }
    /**
     * 上传分片
     * 上传附件分片
     * 判断目录是否存在，如果不存在则创建目录。
     * 进行切片的拷贝，将切片拷贝到指定的目录。
     * 将该分片写入Redis
     *
     * @param chunkDTO
     */
    @Override
    public void uploadChunk(Voide chunkDTO) {
        // 分块的目录
        String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
        logger.info("分块的目录 -> {}", chunkFileFolderPath);
        File chunkFileFolder = new File(chunkFileFolderPath);
        if (!chunkFileFolder.exists()) {
            boolean mkdirs = chunkFileFolder.mkdirs();
            logger.info("创建分片文件夹: {}", mkdirs);
        }
        // 写入分片
        try (InputStream inputStream = chunkDTO.getFile().getInputStream();
            OutputStream outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunkDTO.getChunkNumber()))) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            logger.info("文件标识: {}, chunkNumber: {}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
            // 将该分片写入redis
            long size = saveToRedis(chunkDTO);
            // 把分片保存到数据库
            chunkDTO.setCreateTime(new Date());
            voideMapper.saveToDatabase(chunkDTO);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 合并分片
     *
     * @param identifier
     * @param filename
     */
    public Integer mergeChunk(Voide voide) {
        // 得到分块文件所属的目录
        String chunkFileFolderPath = getChunkFileFolderPath(voide.getIdentifier());
        // 检查分片是否都存在
        File chunkFileFolder = new File(chunkFileFolderPath);
        if (chunkFileFolder.exists() && chunkFileFolder.isDirectory()) {
             File[] files = chunkFileFolder.listFiles();
            if (voide.getTotalChunks() == files.length) {
                // 生成当前时间作为文件夹名
                String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

                // 创建以当前时间命名的文件夹
                File mergeFolder = new File(rootPath + File.separator + "allFile" + File.separator + currentTime);
                if (!mergeFolder.exists()) {
                    mergeFolder.mkdirs();
                }
                // 修改合并后的文件路径，将文件放入新创建的文件夹中
                File mergeFile = new File(mergeFolder.getAbsolutePath() + File.separator + voide.getFilename());
                File[] chunks = chunkFileFolder.listFiles();
                // 切片排序1、2/3、---
                List<File> fileList = Arrays.asList(chunks);
                Collections.sort(fileList, (o1, o2) -> Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName()));
                try {
                    RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
                    byte[] bytes = new byte[1024];
                    for (File chunk : chunks) {
                        RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
                        int len;
                        while ((len = randomAccessFileReader.read(bytes)) != -1) {
                            randomAccessFileWriter.write(bytes, 0, len);
                        }
                        randomAccessFileReader.close();
                    }
                    randomAccessFileWriter.close();

                    // 删除分片文件夹
                    deleteFolder(chunkFileFolder);
                    // 把分片保存到数据库
                    voide.setCreateTime(new Date());
                    // 添加删除标识
                    voide.setDelFlag(0);
                    voide.setFileType("mp4");
                    // 添加文件路径
                    voide.setFilePath(mergeFile.getAbsolutePath());
                    // 添加文件大小
                    voide.setTotalSize(mergeFile.length());
                    voideMapper.saveToDatabase1(voide);
                } catch (Exception e) {
                    e.printStackTrace();
                    return 0;
                }
                return voide.getId();
            }
        }
        return 0;
    }

    /**
     * 删除文件夹
     * @param folder 文件夹
     */
    private void deleteFolder(File folder) {
        try {
            Path folderPath = Paths.get(folder.getAbsolutePath());
            Files.walk(folderPath)
                    .sorted(Comparator.reverseOrder())
                    .map(Path::toFile)
                    .forEach(File::delete);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查分片是否都存在
     * @param chunkFileFolderPath
     * @param totalChunks
     * @return
     */
    private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) {
        try {
            for (int i = 1; i < totalChunks + 1; i++) {
                File file = new File(chunkFileFolderPath + File.separator + i);
                if (file.exists()) {
                    continue;
                } else {
                    return false;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 分片写入Redis
     * 判断切片是否已存在，如果未存在，则创建基础信息，并保存。
     * @param chunkDTO
     */
    private synchronized long saveToRedis(Voide chunkDTO) {
        Set<String> uploaded = (Set<String>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
        if (uploaded == null) {
            uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
            HashMap<String, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("uploaded", uploaded);
            objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
            objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
            objectObjectHashMap.put("path", chunkDTO.getFilename());
            redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
        } else {
            uploaded.add(chunkDTO.getChunkNumber());
            redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
        }
        return uploaded.size();
    }

    /**
     * 得到文件的绝对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFilePath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        String identifierSuffix = identifier.substring(identifier.indexOf("-") + 1);
        return rootPath + File.separator + identifierSuffix + ext;
    }

    /**
     * 得到文件的相对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFileRelativelyPath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        String identifierSuffix = identifier.substring(identifier.indexOf("-") + 1);
        String filePath = "/" + identifierSuffix + "/" + identifier + "/" + identifier + ext;
        return filePath;
    }


    /**
     * 得到分块文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getChunkFileFolderPath(String identifier) {
        return getFileFolderPath(identifier) + File.separator;
    }

    /**
     * 得到文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getFileFolderPath(String identifier) {
        return rootPath + File.separator + "temp" +File.separator + identifier.substring(0,identifier.length()-1)+ File.separator;
    }
}
